/*
 ===============================================================================
 Driver Name		:		ashmem
 Author				:		FANGHR
 License			:		GPL
 Description		:		LINUX DEVICE DRIVER PROJECT
 ===============================================================================
 */

#include"ashmem.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("FANGHR");

static LIST_HEAD(ashmem_lru_list);//unpinned页的lru列表,由ashmem_mutex保护
static unsigned long lru_count;//lru列表中页(range)的数量,由ashmem_mutex保护
static DEFINE_MUTEX(ashmem_mutex);//保护每个ashmem_area和列表,锁定顺序ashmex_mutex -> i_mutex -> i_alloc_sem

static struct kmem_cache* ashmem_area_cachep __read_mostly;
static struct kmem_cache* ashmem_range_cachep __read_mostly;
//内核高速缓存大法

//lru相关实现

/**
 * lru_add():将range添加到lru列表
 * @param range:被添加的内存区
 * 该range首先被添加到lru列表的尾部
 * 然后把range的大小加入lru_count
 */
static inline void lru_add(struct ashmem_range *range) {
	list_add_tail(&range->lru, &ashmem_lru_list);
	lru_count += range_size(range);
}
;

/**
 * lru_del():将某个range从lru列表中删除
 * @param range:被删除的内存区
 * 该range首先被从lru列表中删除
 * 然后在lru_count中减去其大小
 */
static inline void lru_del(struct ashmem_range *range) {
	list_del(&range->lru);
	lru_count -= range_size(range);
}

/**
 * range_alloc():为一个新的ashmem_area对象分配内存并初始化
 * @param asma:相关的ashmem_area
 * @param prev_range:asma->unpinned列表中的前一个对象
 * @param purged:初始化清除状态（ASMEM_NOT_PURGED或ASHMEM_WAS_PURGED）
 * @param start:起始内存页
 * @param end:结束内存页
 * @return:0成功,-ENOMEM失败
 */
static int range_alloc(struct ashmem_area *asma,
		struct ashmem_range *prev_range, unsigned int purged, size_t start,
		size_t end) {
	PTRACE;
	struct ashmem_range *range;
	range = kmem_cache_zalloc(ashmem_range_cachep, GFP_KERNEL);
	if (unlikely(!range))
		return -ENOMEM;
	range->asma = asma;
	range->pgstart = start;
	range->pgend = end;
	range->purged = purged;
	list_add_tail(&range->unpinned, &prev_range->unpinned);
	if (range_on_lru(range))
		lru_add(range);
	return 0;
}

/**
 * range_del():删除并释放一个ashmem_range对象
 * @param range:先前分配的相关的ashmem_range
 */
static void range_del(struct ashmem_range *range) {
	PTRACE;
	list_del(&range->unpinned);
	if (range_on_lru(range))
		lru_del(range);
	kmem_cache_free(ashmem_range_cachep, range);
}

/**
 * range_shrink():收缩一个ashmem_range
 * @param range:被收缩的range
 * @param start:新range的起始字节
 * @param end:新范围的结束字节
 * 该函数不会以任何方式修改现有range内的数据
 * 它只是缩小range的界限
 * 理论上最终可以将range大小更改为range_resize
 * 并在新range较大时扩展lru_count
 */
static inline void range_shrink(struct ashmem_range *range, size_t start,
		size_t end) {
	PTRACE;
	size_t pre = range_size(range);
	range->pgstart = start;
	range->pgend = end;
	if (range_on_lru(range))
		lru_count -= pre - range_size(range);
}

//ashmem基础文件:ashmem机制的实现需要一个文件作为支撑

/**
 * ashmem_open():open()时被调用
 * @param inode:基础文件的inode结构
 * @param file:基础文件的file结构
 * @return:0成功,其他失败
 */
static int ashmem_open(struct inode *inode, struct file *file) {
	PTRACE;
	struct ashmem_area *asma;
	int ret;
	ret = generic_file_open(inode, file);
	if (unlikely(ret))
		return ret;
	asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
	if (unlikely(!asma))
		return -ENOMEM;
	INIT_LIST_HEAD(&asma->unpinned_list);
	memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
	asma->prot_mask = PROT_MASK;
	file->private_data = asma;
	return 0;
}

/**
 * ashmem_release():close()时被调用
 * @param inode:基础文件的inode结构
 * @param file:基础文件的file结构
 * @return:0成功,其他失败
 */
static int ashmem_release(struct inode *inode, struct file *file) {
	PTRACE;
	struct ashmem_area *asma = file->private_data;
	struct ashmem_range *range, *next;
	mutex_lock(&ashmem_mutex);
	list_for_each_entry_safe(range,next,&asma->unpinned_list,unpinned)
		range_del(range);
	mutex_unlock(&ashmem_mutex);
	if (asma->file)
		fput(asma->file);
	kmem_cache_free(ashmem_area_cachep, asma);
	return 0;
}

/**
 * ashmem_read():从ashmem基础文件中读取
 * @file:基础文件
 * @buf:进程的缓冲区
 * @len:读取字节数
 * @pos:要读取的第一个字节的位置
 * @return:0成功,其他失败
 */
static ssize_t ashmem_read(struct file *file, char __user *buf, size_t len,
		loff_t *pos) {
	PTRACE;
	struct ashmem_area *asma = file->private_data;
	int ret = 0;
	mutex_lock(&ashmem_mutex);
	//如果映射区的size未设置或者为0,直接返回EOF
	if (asma->size == 0)
		goto out_unlock;
	if (!asma->file) {
		ret = -EBADF;
		goto out_unlock;
	}
	mutex_unlock(&ashmem_mutex);

	//asma和asma->file在锁外面使用
	//我们假设asma->file一旦被内核设定就不会更改或者删除,直到所有对file的引用都被删除后者ashmem_release()被调用
	ret = __vfs_read(asma->file, buf, len, pos);
	if (ret >= 0) {
		//更新基础文件的pos,因为f_ops->read()不会更新pos
		asma->file->f_pos = *pos;
	}
	out_unlock: mutex_unlock(&ashmem_mutex);
	return ret;
}

/*
 * ashmem_write():该函数什么都不做
 */
__attribute__((unused)) static ssize_t ashmem_write(struct file *file, char __user *buf, size_t len,
		loff_t *pos) {
	PTRACE;
	return 0;
}

//慎用goto？不存在的

/**
 * ashmem_llseek():lseek()时被调用
 * @param file
 * @param offset
 * @param origin
 * @return:0成功,其他失败
 */
static loff_t ashmem_llseek(struct file*file, loff_t offset, int origin) {
	PTRACE;
	struct ashmem_area *asma = file->private_data;
	int ret;

	mutex_lock(&ashmem_mutex);
	if (asma->size == 0) {
		ret = -EINVAL;
		goto out;
	}
	if (!asma->file) {
		ret = -EBADF;
		goto out;
	}
	ret = vfs_llseek(asma->file, offset, origin);
	if (ret < 0)
		goto out;

	file->f_pos = asma->file->f_pos;
	out: mutex_unlock(&ashmem_mutex);
	return ret;
}

static inline vm_flags_t calc_vm_may_flags(unsigned long prot) {
	return _calc_vm_trans(prot,PROT_READ,VM_MAYREAD) | _calc_vm_trans(prot,
			PROT_WRITE, VM_MAYWRITE) | _calc_vm_trans(prot, PROT_EXEC,
			VM_MAYEXEC);
}

/**
 *
 * @param file
 * @param vma
 * @return
 */
static int ashmem_mmap(struct file* file, struct vm_area_struct *vma) {
	PTRACE;
	struct ashmem_area *asma = file->private_data;
	int ret = 0;
	mutex_lock(&ashmem_mutex);
	//映射前应该先SET_SIZE
	if (unlikely(!asma->size)) {
		ret = -EINVAL;
		goto out;
	}
	if (unlikely((vma->vm_flags& ~calc_vm_flag_bits(asma->prot_mask))&calc_vm_prot_bits(PROT_MASK,0))) {
		ret = -EPERM;
		goto out;
	}
	vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
	if (!asma->file) {
		char *name = ASHMEM_NAME_DEF;
		struct file *vmfile;
		if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
			name = asma->name;
		//初始化基础文件
		vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
		if (unlikely(IS_ERR(vmfile))) {
			ret = PTR_ERR(vmfile);
			goto out;
		}
		asma->file = vmfile;
	}
	get_file(asma->file);
	if (vma->vm_flags & VM_SHARED) {
		ret = shmem_zero_setup(vma);
		if (ret) {
			fput(asma->file);
			goto out;
		}
	}
	if (vma->vm_file)
		fput(vma->vm_file);
	vma->vm_file = asma->file;
	out: mutex_unlock(&ashmem_mutex);
	return ret;
}

/**
 * ashmem_shrink():我们的缓存收缩器，被mm/vmscan.c中的函数调用
 * 'nr_to_scan'是要释放的对象的数量
 * 返回值是已释放对象的数量，如果我们无法进行锁定（由于gfp_mask），则无法继续执行。
 * @param shrink
 * @param sc
 */
static unsigned long ashmem_shrink_scan(struct shrinker *shrink,
		struct shrink_control *sc) {
	PTRACE;
	struct ashmem_range *range, *next;
	unsigned long freed = 0;
	if (!(sc->gfp_mask & __GFP_FS))
		return SHRINK_STOP;
	mutex_lock(&ashmem_mutex);
	list_for_each_entry_safe(range,next,&ashmem_lru_list,lru) {
		loff_t start = range->pgstart * PAGE_SIZE;
		loff_t end = (range->pgend + 1) * PAGE_SIZE;
		vfs_fallocate(range->asma->file,
				FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, start, end - start);
		range->purged = ASHMEM_WAS_PURGED;
		lru_del(range);
		freed += range_size(range);
		if (--sc->nr_to_scan <= 0)
			break;
	}
	mutex_unlock(&ashmem_mutex);
	return freed;
}

//妈的老子写个毛注释

static unsigned long ashmem_shrink_count(struct shrinker *shrink,
		struct shrink_control *sc) {
	//lru_count是lru上的页数，而不是列表中对象的数量。
	//这意味着扫描功能需要返回已释放页面的数量，而不是扫描的对象数量。
	PTRACE;
	return lru_count;
}

static struct shrinker ashmem_shrinker = {
		.count_objects = ashmem_shrink_count, .scan_objects =
				ashmem_shrink_scan, .seeks = DEFAULT_SEEKS * 4, };

static int set_prot_mask(struct ashmem_area *asma, unsigned long prot) {
	PTRACE;
	int ret = 0;
	mutex_lock(&ashmem_mutex);
	//用户只能删除，不能添加保护位
	if (unlikely((asma->prot_mask & prot) != prot)) {
		ret = -EINVAL;
		goto out;
	}
	if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
		prot |= PROT_EXEC;
	asma->prot_mask = prot;

	out: mutex_unlock(&ashmem_mutex);
	return ret;
}

static int set_name(struct ashmem_area *asma, void __user *name) {
	PTRACE;
	int len;
	int ret = 0;
	//内存溢出？我不管
	char local_name[ASHMEM_NAME_LEN];
	//在执行copy_from_user时持有ashmem_mutex可能会导致数据中止，从而尝试访问mmap_sem
	//如果另一个线程调用了ashmem_mmap，那么它将持有信号量并且将等待ashmem_mutex，从而导致死锁
	//我们将释放该互斥锁，并将该名称复制到一个不需要保护的局部变量，然后将该局部变量复制到具有锁定的结构成员。
	len = strncpy_from_user(local_name, name, ASHMEM_NAME_LEN);
	if (len < 0)
		return len;
	if (len == ASHMEM_NAME_LEN)
		local_name[ASHMEM_NAME_LEN - 1] = '\0';
	mutex_lock(&ashmem_mutex);
	//不能更改现有映射的名称
	if (unlikely(asma->file))
		ret = -EINVAL;
	else
		strcpy(asma->name + ASHMEM_NAME_PREFIX_LEN, local_name);
	mutex_unlock(&ashmem_mutex);
	return ret;
}

static int get_name(struct ashmem_area *asma, void __user *name) {
	PTRACE;
	int ret = 0;
	size_t len;
	//我们将在持有锁的情况下从asma复制内容到local_name
	//然后我们可以安全地将其复制到用户空间，而不必锁定任何锁
	//所以即使我们继续等待mmap_sem，它也不会导致死锁
	char local_name[ASHMEM_NAME_LEN];
	mutex_lock(&ashmem_mutex);
	if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') {
		//仅复制“len”而不是ASHMEM_NAME_LEN字节可以防止我们将一个用户的堆栈泄露给另一个用户
		//美好的愿望罢了
		len = strlen(asma->name + ASHMEM_NAME_PREFIX_LEN) + 1;
		memcpy(local_name, asma->name + ASHMEM_NAME_PREFIX_LEN, len);
	} else {
		len = sizeof(ASHMEM_NAME_DEF);
		memcpy(local_name, ASHMEM_NAME_DEF, len);
	}
	mutex_unlock(&ashmem_mutex);
	//现在我们只是从栈变量复制到userland
	//不锁定
	if (unlikely(copy_to_user(name, local_name, len)))
		ret = -EFAULT;
	return ret;
}

/**
 * ashmem_pin():固定给定的ashmem区域
 * @param asma
 * @param pgstart
 * @param pgend
 * @return:是否先前已清除（ASHMEM_WAS_PURGED）或未清除（ASHMEM_NOT_PURGED)
 * 调用者必须持有ashmem_mutex
 */
static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend) {
	PTRACE;
	struct ashmem_range *range, *next;
	int ret = ASHMEM_NOT_PURGED;

	list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {
		//移动过去的适用页面
		//不绕远路
		if (range_before_page(range, pgstart))
			break;
		if (page_range_in_range(range, pgstart, pgend)) {
			ret |= range->purged;
			//用户可以要求我们固定跨越多个范围的页面，或者钉住未固定的页面
			//所以这他妈很麻烦
			//四种情况：
			//1.请求范围包含现有range，所以我们只要删除整个匹配的range
			if (page_range_subsumes_range(range, pgstart, pgend)) {
				range_del(range);
				continue;
			}
			//2.请求的range与现有range的开始重叠，因此我们只更新该range
			if (range->pgstart >= pgstart) {
				range_shrink(range, pgend + 1, range->pgend);
				continue;
			}
			//3.请求的range与现有的结尾重叠,所以我们只更新该range
			if (range->pgend <= pgend) {
				range_shrink(range, range->pgstart, pgstart - 1);
				continue;
			}
			//4.请求的range在现有range的中间,于是我们为它的下半部分分配了一个新的range,并调整第一个range的端点
			//fuck
			range_alloc(asma, range, range->purged, pgend + 1, range->pgend);
			range_shrink(range, range->pgstart, pgstart - 1);
			break;
		}
	}
	return ret;
}

/**
 * ashmem_unpin():对给定ashmem区域取消固定
 * @param asma
 * @param pgstart
 * @param pgend
 * @return:成功返回0
 * 调用函数应持有ashmem_mutex
 */
static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend) {
	PTRACE;
	struct ashmem_range *range, *next;
	unsigned int purged = ASHMEM_NOT_PURGED;

	restart: list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {
		//较短的路径
		//让内核忙去吧
		if (range_before_page(range, pgstart))
			break;
		//用户可以要求我们取消固定完全固定或部分固定的页面
		//有两种情况:
		if (page_range_subsumed_by_range(range, pgstart, pgend))
			return 0;
		if (page_range_in_range(range, pgstart, pgend)) {
			pgstart = min_t(size_t, range->pgstart, pgstart), pgend
					= max_t(size_t, range->pgend, pgend);
			purged |= range->purged;
			range_del(range);
			//听说goto不好
			goto restart;
		}
	}
	return range_alloc(asma, range, purged, pgstart, pgend);
}

/**
 * ashmem_get_pin_status()
 * @param asma
 * @param pgstart
 * @param pgend
 * @return:如果给定range中的任何页是未固定的，则返回ASHMEM_IS_UNPINNED，否则返回ASHMEM_IS_PINNED
 */
static int ashmem_get_pin_status(struct ashmem_area *asma, size_t pgstart,
		size_t pgend) {
	PTRACE;
	struct ashmem_range *range;
	int ret = ASHMEM_IS_PINNED;

	list_for_each_entry(range, &asma->unpinned_list, unpinned) {
		if (range_before_page(range, pgstart))
			break;
		if (page_range_in_range(range, pgstart, pgend)) {
			ret = ASHMEM_IS_UNPINNED;
			break;
		}
	}
	return ret;
}

static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd,
		void __user *p) {
	PTRACE;
	struct ashmem_pin pin;
	size_t pgstart, pgend;
	int ret = -EINVAL;

	if (unlikely(!asma->file))
		return -EINVAL;

	if (unlikely(copy_from_user(&pin, p, sizeof(pin))))
		return -EFAULT;

	/* per custom, you can pass zero for len to mean "everything onward" */
	if (!pin.len)
		pin.len = PAGE_ALIGN(asma->size) - pin.offset;

	if (unlikely((pin.offset | pin.len) & ~PAGE_MASK))
		return -EINVAL;

	if (unlikely(((__u32) -1) - pin.offset < pin.len))
		return -EINVAL;

	if (unlikely(PAGE_ALIGN(asma->size) < pin.offset + pin.len))
		return -EINVAL;

	pgstart = pin.offset / PAGE_SIZE;
	pgend = pgstart + (pin.len / PAGE_SIZE) - 1;

	mutex_lock(&ashmem_mutex);

	switch (cmd) {
	case ASHMEM_PIN:
		ret = ashmem_pin(asma, pgstart, pgend);
		break;
	case ASHMEM_UNPIN:
		ret = ashmem_unpin(asma, pgstart, pgend);
		break;
	case ASHMEM_GET_PIN_STATUS:
		ret = ashmem_get_pin_status(asma, pgstart, pgend);
		break;
	}

	mutex_unlock(&ashmem_mutex);

	return ret;
}

static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
	PTRACE;
	struct ashmem_area *asma = file->private_data;
	long ret = -ENOTTY;

	switch (cmd) {
	case ASHMEM_SET_NAME:
		ret = set_name(asma, (void __user *) arg);
		break;
	case ASHMEM_GET_NAME:
		ret = get_name(asma, (void __user *) arg);
		break;
	case ASHMEM_SET_SIZE:
		ret = -EINVAL;
		if (!asma->file) {
			ret = 0;
			asma->size = (size_t) arg;
		}
		break;
	case ASHMEM_GET_SIZE:
		ret = asma->size;
		break;
	case ASHMEM_SET_PROT_MASK:
		ret = set_prot_mask(asma, arg);
		break;
	case ASHMEM_GET_PROT_MASK:
		ret = asma->prot_mask;
		break;
	case ASHMEM_PIN:
	case ASHMEM_UNPIN:
	case ASHMEM_GET_PIN_STATUS:
		ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);
		break;
	case ASHMEM_PURGE_ALL_CACHES:
		ret = -EPERM;
		if (capable(CAP_SYS_ADMIN)) {
			struct shrink_control sc = { .gfp_mask = GFP_KERNEL, .nr_to_scan =
					LONG_MAX, };
			ret = ashmem_shrink_count(&ashmem_shrinker, &sc);
			ashmem_shrink_scan(&ashmem_shrinker, &sc);
		}
		break;
	}

	return ret;
}

//支持64位平台上的32位用户空间
#ifdef CONFIG_COMPAT
static long compat_ashmem_ioctl(struct file *file, unsigned int cmd,
		unsigned long arg)
{
	PTRACE;
	switch (cmd) {
		case COMPAT_ASHMEM_SET_SIZE:
		cmd = ASHMEM_SET_SIZE;
		break;
		case COMPAT_ASHMEM_SET_PROT_MASK:
		cmd = ASHMEM_SET_PROT_MASK;
		break;
	}
	return ashmem_ioctl(file, cmd, arg);
}
#endif

static struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open =
		ashmem_open, .release = ashmem_release, .read = ashmem_read, .llseek =
		ashmem_llseek, .mmap = ashmem_mmap, .unlocked_ioctl = ashmem_ioctl,
#ifdef CONFIG_COMPAT
		.compat_ioctl = compat_ashmem_ioctl,
#endif
		};

static struct miscdevice ashmem_misc = { .minor = MISC_DYNAMIC_MINOR, .name =
		DRIVER_NAME, .fops = &ashmem_fops, };

//千呼万唤屎出来
static int __init ashmem_init(void) {
	PTRACE;
	int ret = 0;
	ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
			sizeof(struct ashmem_area), 0, 0, NULL);
	if (unlikely(!ashmem_area_cachep)) {
		PERR("failed to create slab cache\n");
		return -ENOMEM;
	}
	ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
			sizeof(struct ashmem_range), 0, 0, NULL);
	if (unlikely(!ashmem_range_cachep)) {
		PERR("failed to create slab cache\n");
		return -ENOMEM;
	}
	ret = misc_register(&ashmem_misc);
	if (unlikely(ret)) {
		PERR("failed to register misc device!\n");
		return ret;
	}
	register_shrinker(&ashmem_shrinker);
	PINFO("initialized\n");
	return 0;
}

static void __exit ashmem_exit(void)
{
	PTRACE;
	unregister_shrinker(&ashmem_shrinker);
	misc_deregister(&ashmem_misc);
	kmem_cache_destroy(ashmem_range_cachep);
	kmem_cache_destroy(ashmem_area_cachep);
	PINFO("unloaded\n");
}

module_init(ashmem_init);
module_exit(ashmem_exit);

